/*
 * UMLSynch plugin for Eclipse
 * http://umlsynch.googlecode.com
 *
 * Copyright 2008-2010, Evgeny Alexeyev
 * Licensed under the EPL - v1.0
 * http://www.eclipse.org/legal/epl-v10.html
 *
 */

package com.googlecode.umlsynch.eclipse.astutils;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.index.IIndexManager;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICElementVisitor;
import org.eclipse.cdt.core.model.ICModel;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IField;
import org.eclipse.cdt.core.model.IMethodDeclaration;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.internal.core.model.Structure;
import org.eclipse.cdt.internal.core.model.Variable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;

import com.googlecode.umlsynch.EclipseOperationServer.VPSDEClassDescription;
import com.googlecode.umlsynch.EclipseOperationServer.VPSDEClassDescription.VPSDEClassMember;
import com.googlecode.umlsynch.EclipseOperationServer.VPSDEClassDescription.VPSDEClassOperation;
import com.googlecode.umlsynch.eclipse.rpc.Debug.TEclipseDebug;
import com.googlecode.umlsynch.rpc.TClassDescriptor;
import com.googlecode.umlsynch.rpc.TClassMember;
import com.googlecode.umlsynch.rpc.TClassOperation;
import com.googlecode.umlsynch.rpc.TClassOperationParameters;
import com.googlecode.umlsynch.rpc.TFileDescriptor;

public class TranslationUnitUtils {
	private TEclipseDebug debug = new TEclipseDebug("TU");
	
	private static TranslationUnitUtils instance = new TranslationUnitUtils();
	
	public static TranslationUnitUtils GetInstance() {
		return instance;
	}
	
	private TranslationUnitUtils() {}
	
	private List<ICElement> FindElements(ITranslationUnit translationUnit, String searchPattern, String searchType) {
		if (translationUnit == null) {
			return null;
		}

		class VariableFilter implements ICElementVisitor {
			public List<ICElement> foundElement = new ArrayList();

			public int SEARCH_BY_VARIABLE_NAME = 0;
			public int SEARCH_BY_VARIABLE_TYPE = 1;
			public int SEARCH_BY_CLASS_NAME = 2;
			public int SEARCH_BY_CLASS_NAME_PATTERN = 3;
			
			public int searchType  = -1;

			private Pattern searchPattern = null;
			private Matcher matcher = null;

			public void SetSearchType(int sType) {
				searchType = sType;
			}
			
			public void SetSearchPattern(String searchNamePattern) {
				try {
					searchPattern = Pattern.compile(searchNamePattern);

				} catch (Exception e) {
					e.printStackTrace();							
				}
			}
			
			@Override			
			public boolean visit(ICElement treeElement) throws CoreException {
				switch (searchType) {
				case 0:
					if (treeElement instanceof Variable) {
						matcher = searchPattern.matcher(treeElement.getElementName());
						if (matcher.matches()) {
							foundElement.add(treeElement);
						}
					}
					break;
				case 1:
					if (treeElement instanceof Variable) {
						Variable var = (Variable)treeElement;
						matcher = searchPattern.matcher(var.getTypeName());
						if (matcher.matches()) {
							foundElement.add(treeElement);
						}
					}
					break;
				case 2:
					if (treeElement instanceof Structure) {
						Structure str = (Structure)treeElement;
						if (str.getElementType() == 65) {
							matcher = searchPattern.matcher(treeElement.getElementName());
							if (matcher.matches()) {
								foundElement.add(treeElement);
							}
						}
					}
					break;
				case 3:
					if (treeElement instanceof Structure) {
						Structure str = (Structure)treeElement;
						if (str.getElementType() == 65) {
							matcher = searchPattern.matcher(treeElement.getElementName());
							if (matcher.matches()) {
								foundElement.add(treeElement);
							}
						}
					}
					break;
				default:
					return true;					
				}
				return true;
			}};

		VariableFilter varNameFilter = new VariableFilter();
		varNameFilter.SetSearchPattern(searchPattern);
		if (searchType.equalsIgnoreCase("ClassByName")) {
			varNameFilter.SetSearchType(varNameFilter.SEARCH_BY_CLASS_NAME);	
		}

		if (searchType.equalsIgnoreCase("ClassByNamePattern")) {
			varNameFilter.SetSearchType(varNameFilter.SEARCH_BY_CLASS_NAME_PATTERN);	
		}
										 
		if (searchType.equalsIgnoreCase("VariableByName")) {
			varNameFilter.SetSearchType(varNameFilter.SEARCH_BY_VARIABLE_NAME);	
		}

		if (searchType.equalsIgnoreCase("VariableByType")) {
			varNameFilter.SetSearchType(varNameFilter.SEARCH_BY_VARIABLE_TYPE);	
		}

		
		try {
			translationUnit.accept(varNameFilter);
		} catch (CoreException e) {
			
		}
		
		return varNameFilter.foundElement;
	}

	private boolean GetProjectAndPath(TFileDescriptor fileDescriptor, String projName, String subPath) {
		String[] pathSplit = fileDescriptor.path.split("/");
		if ((pathSplit != null) && (pathSplit.length >= 1)) {
			projName = pathSplit[0];
			if (pathSplit.length > 1) {
				subPath = fileDescriptor.path.substring(projName.length() + 1, fileDescriptor.path.length());
			}
			return true;
		}		
		
		return false;
	}

	public TClassDescriptor[] FindClassByName(TFileDescriptor[] searchPaths,
			final String searchNamePattern) {
		
		List<TClassDescriptor> foundDescriptors = new ArrayList();
		
		for (int p=0; (searchPaths != null) && (p<searchPaths.length); ++p) {
			String projName = null;
			String path = null;
			if (!GetProjectAndPath(searchPaths[p], projName, path)) {
				return null;
			}

			ICModel cModel = CoreModel.getDefault().getCModel();
			ICProject proj = cModel.getCProject(projName);
			if (proj == null) {
				debug.Error("Can not find cModel of project: " + projName);
				return null;
			}
			
			ICElement elem = GetTranslationUnitByPath(proj, path);
			List<ICElement> foundElements = this.FindElements((ITranslationUnit) elem, searchNamePattern, "ClassByName");
			Iterator<ICElement> i = foundElements.iterator();

			while (i.hasNext()) {
				ICElement next = i.next();
				Structure strr = (Structure)next;
				TClassDescriptor nextClassDescriptor = new TClassDescriptor();
				nextClassDescriptor.name = strr.getElementName();
				nextClassDescriptor.file.path = strr.getPath().toPortableString();
				foundDescriptors.add(nextClassDescriptor);
			}
			
		}
		
		if (foundDescriptors.isEmpty()) {
			return null;
		}
		
		return (TClassDescriptor[]) foundDescriptors.toArray();
	}

	public String FindValuesByType(TFileDescriptor sourceFile, String namePattern) {
		String projName = null;
		String path = null;
		if (!GetProjectAndPath(sourceFile, projName, path)) {
			return null;
		}

		ICModel cModel = CoreModel.getDefault().getCModel();
		ICProject proj = cModel.getCProject(projName);
		if (proj == null) {
			debug.Error("Can not find cModel of project: " + projName);
			return null;
		}
		
		ICElement elem = GetTranslationUnitByPath(proj, path);
		List<ICElement> foundElements = 
			this.FindElements((ITranslationUnit) elem, namePattern, "VariableByType");
		Iterator<ICElement> i = foundElements.iterator();
		
		String source = "";
		
		while (i.hasNext()) {
			source += "$$$";
			Variable next = (Variable) i.next();
			try {
				source += next.getSource();
				
			} catch (CModelException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return (!source.isEmpty()) ? source:null;
	}

	public String FindValuesByName(TFileDescriptor sourceFile, String namePattern) {
		String projName = null;
		String path = null;
		if (!GetProjectAndPath(sourceFile, projName, path)) {
			return null;
		}

		ICModel cModel = CoreModel.getDefault().getCModel();
		ICProject proj = cModel.getCProject(projName);
		if (proj == null) {
			debug.Error("Can not find cModel of project: " + projName);
			return null;
		}
		
		ICElement elem = GetTranslationUnitByPath(proj, path);
		List<ICElement> foundElements = 
			this.FindElements((ITranslationUnit) elem, namePattern, "VariableByName");
		Iterator<ICElement> i = foundElements.iterator();
		
		String source = "";
		
		while (i.hasNext()) {
			source += "$$$";
			Variable next = (Variable) i.next();
			try {
				source += next.getSource();
				
			} catch (CModelException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return (!source.isEmpty()) ? source:null;
	}

	
	private ICElement GetTranslationUnitByPath(ICProject prj, String path) {
		IPath elementPath = new Path(path);
		ICElement cElemenet = null;
		try {
			cElemenet = prj.findElement(elementPath);
		} catch (CModelException e) {

		}				

		return cElemenet;		
	}

	public TClassDescriptor UpdateClassData(TClassDescriptor desc) {

		Structure foundClass = GetClassStructure(desc);
		if (foundClass != null) {
			debug.Note("UpdateClassData");
			debug.Note("UpdateClassData" + foundClass.toDebugString());
		} else {
			debug.Note("UpdateClassData found null");
			return null;
		}
		
		try {
			debug.Note("1.1");
			IMethodDeclaration[] methods = foundClass.getMethods();
			IField[] fields = foundClass.getFields();
			debug.Note("1.2");
			desc.members = new TClassMember[fields.length];
			desc.operations = new TClassOperation[methods.length];
			debug.Note("1.3");
			for (int i =0; i<fields.length; ++i) {
				desc.members[i] = new TClassMember();
				if (desc.members[i] == null) {
					debug.Error("Field not available: " + i);					
				} else {
					desc.members[i].name = fields[i].getElementName();
					desc.members[i].visibility = fields[i].getVisibility().name();
				}
			}
			debug.Note("1.4");
			for (int i =0; i<methods.length; ++i) {
				debug.Note("1.4.1");
				desc.operations[i] = new TClassOperation();
				if (desc.operations[i] == null) {
					debug.Error("Operation not available: " + i);					
				} else {
					debug.Note("1.4.2");
					desc.operations[i].name = methods[i].getElementName();
					debug.Note("1.4.3");
					desc.operations[i].visibility = methods[i].getVisibility().name();
					debug.Note("1.4.4");
					int pcount = methods[i].getNumberOfParameters();
					String[] types = methods[i].getParameterTypes();
					debug.Note("1.4.5");
					if ((pcount > 0 ) && (types.length > 0)) {
						desc.operations[i].parameters =
							new TClassOperationParameters[pcount];
						debug.Note("1.4.6 = " + types.length);
						
						debug.Note("1.4.7 = " + pcount);
						pcount = (pcount > types.length) ? types.length:pcount;
						--pcount;
						debug.Note("1.4.8  -- " + pcount);
						for (; pcount>=0; --pcount) {
							debug.Note("1.4.9." + pcount);
							desc.operations[i].parameters[pcount] = new TClassOperationParameters();
							desc.operations[i].parameters[pcount].type = 
								types[pcount];
						}
					}
						
					
					
				}
			}
			debug.Note("1.5");
			
		} catch (CModelException e) {
			debug.Note("UpdateClassData exception" + e.getLocalizedMessage());
		}
		return desc;
	}

	private Structure GetClassStructure(TClassDescriptor desc) {
		
		ICModel cModel = CoreModel.getDefault().getCModel();
		
		ICProject proj = cModel.getCProject(desc.file.project);
		if (proj == null) {
			debug.Error("Can not find cModel of project: " + desc.file.project);
			return null;
		}
		debug.Note("1");
		ICElement translationUnit = this.GetTranslationUnitByPath(proj, desc.file.path);
		debug.Note("2");
		String[] splitName = desc.name.split("::");
		String className = (splitName.length > 0) ? splitName[splitName.length-1]: desc.name; 
		debug.Note("3");
		List<ICElement> elements = this.FindElements((ITranslationUnit) translationUnit, className, "ClassByName");
		debug.Note("5");

		if ((elements == null) || (elements.size() != 1))
			return null;

		debug.Note("4");
		return (Structure) elements.get(0);	
	}
	
	public TClassDescriptor[] FindClassBases(
			TClassDescriptor classData) {

		Structure ref = this.GetClassStructure(classData);
		
		if (ref == null) 
			return null;
		
		String[] names = ref.getSuperClassesNames();
		if ((names != null)) {
			TClassDescriptor[] ret = new TClassDescriptor[names.length];
			for (int i=0; (i<names.length); ++i) {
				ret[i] = new TClassDescriptor();
				ret[i].name = names[i];
			}
			return ret;
		}
		
		return null;
	}
	
	public TClassDescriptor[] FindClassNested(
			TClassDescriptor classData) {

		Structure ref = this.GetClassStructure(classData);
		if (ref == null) 
			return null;
		
		List<ICElement> subclasses;
		try {
			subclasses = ref.getChildrenOfType(ref.getElementType());
			if ((subclasses != null)) {
				TClassDescriptor[] ret = new TClassDescriptor[subclasses.size()];
				for (int i=0; (i<subclasses.size()); ++i) {
					ret[i] = new TClassDescriptor();
					ret[i].name = subclasses.get(i).getElementName();				
				}
				return ret;
			}

		} catch (CModelException e) {
		}
		
		return null;
	}
	
	public String GetProjectPath(String projectName) {
		ICModel cModel= CoreModel.getDefault().getCModel();
		IIndexManager manager = CCorePlugin.getIndexManager();

		ICProject cProject = cModel.getCProject(projectName);
		if (cProject == null) {
			debug.Error("Can not find cModel of project: " + projectName);
			return null;
		}

		String prefix = cProject.getLocationURI().toString();
		prefix = prefix.substring(6, prefix.length());
		return prefix;
	}
	
	public TClassDescriptor[] DynamcCastListToArray(List<TClassDescriptor> list) {
		
		if ((list == null) || list.isEmpty())
			return null;
		
		TClassDescriptor[] result = new TClassDescriptor[list.size()];
		
		for (int i=0; i< list.size(); ++i){
			result[i] = list.get(i);
		}
		return result;
	}

	public void FindClassSpecifiers(String fileName, TClassDescriptor classData, String relationType) {
		
		boolean isAssociationSearch = false;
		boolean isAggregationSearch = false;
		boolean isDependencySearch = false;
		debug.Debug("R:1");
		if (relationType.equals("Association")) {
			debug.Debug("R:1Ass");
			isAssociationSearch = true;	
		} else if (relationType.equals("Aggregation")) {
			debug.Debug("R:1Agg");
			isAggregationSearch = true;
		} else if (relationType.equals("Dependency")) {
			debug.Debug("R:1Dep");
			isDependencySearch = true;
		} else {
			debug.Debug("R:1 nothing");
			return;
		}
		
		ICModel cModel = CoreModel.getDefault().getCModel();
		
		ICProject proj = cModel.getCProject(classData.file.project);
		if (proj == null) {
			debug.Error("Can not find cModel of project: " + classData.file.project);
			return ;
		}

		int pos = fileName.indexOf(classData.file.project) + classData.file.project.length() +  1; 
		String path = fileName.substring(pos, fileName.length()); 
		ITranslationUnit trans = (ITranslationUnit) this.GetTranslationUnitByPath(proj, path);
		
		List<ICElement> result = FindElements(trans, "[a-zA-Z0-9]*", "ClassByNamePattern");
		for (int i=0; (result != null) && (i < result.size()); ++i) {
			ICElement elem = result.get(i);
			if (elem instanceof Structure) {
				Structure strElem = (Structure)elem;
				try {
					IField[] fields = strElem.getFields();
					IMethodDeclaration[] methods = strElem.getMethods();
					boolean isAssociation = false;
					boolean isAggregation = false;
					boolean isDependency  = false;
					
					for (int j=0; j<fields.length; ++j) {
						if (fields[j].getTypeName() != null) {
							debug.Debug("R:6.1");
							String type = fields[j].getTypeName();
							debug.Debug("R:6.2:" + type);
							if (type.equalsIgnoreCase(classData.name)) {
								debug.Debug("R:6.3");
								isAggregation = true;
							} else if (type.contains(classData.name)) {
								debug.Debug("R:6.4");
								isAssociation = true;
							}
						}
					}
					debug.Debug("R:7");
					for (int j=0; j<methods.length; ++j) {

						String[] params = methods[j].getParameterTypes();
						
						for (int k=0; (params != null) && (k<params.length); ++k) {

							String type = params[k];
							debug.Debug("R:7.1:" + type);
							if (type.contains(classData.name)) {
								isDependency = true;
							} 

						}
						
						String ftype = methods[j].getReturnType();
						debug.Debug("R:7.2:" + ftype);
						if (ftype.contains(classData.name)) {
							isAssociation = true;
						}
					}
					debug.Debug("R:8");
					if ( !isAssociation && !isDependency && !isAggregation) {
						// if type mentioned this class, but 
						// there is no dependency in fields and methods
						// therefore it is dependency
						debug.Debug("R:9");
						isDependency = true;
					}

					
					debug.Debug("R:10");
					if ((isAggregation && isAggregationSearch)
							|| (isAssociation && isAssociationSearch)
							|| (isDependency && isDependencySearch)) {
						
						VPSDEClassDescription res = new VPSDEClassDescription();
						res.classFileName = path;
						res.className = strElem.getElementName();
						res.projectName = classData.file.project;
						debug.Debug("Add: " + res.className);
						//result.add(res);
					}

					
				} catch (CModelException e) {
					debug.Error("No fields found by TU");
				}
				
			}
		}
	}

	public int HasAssociationRelation(String projectName, String fileName, String className, String classInFile) {
		ICModel cModel = CoreModel.getDefault().getCModel();
		
		ICProject proj = cModel.getCProject(projectName);
		if (proj == null) {
			debug.Error("Can not find cModel of project: " + projectName);
			return -1;
		}
 
		ITranslationUnit trans = (ITranslationUnit) this.GetTranslationUnitByPath(proj, fileName);
		
		List<ICElement> result = FindElements(trans, classInFile, "ClassByName");
		
		for (int i=0; (result!=null) && (i<result.size()); ++i) {
			ICElement elem = result.get(i);
			if (elem instanceof Structure) {
				Structure selem = (Structure)elem;
				try {
					IField[] fields = selem.getFields();
					for (int j=0; (fields !=null) && (j<fields.length); ++j) {
						String type = fields[j].getTypeName();
						if (type != null) {
							if (type.equalsIgnoreCase(className)) {
								return 1; // composition
							} else if (type.contains(className)) {
								return 0; // aggregation or association
							}
						}
					}
				} catch (CModelException e) {
					debug.Error("No fields found");
				}
				
			}
		}

		return -1;
	}

}
